﻿/* EntitiesToDTOs. Copyright (c) 2011. Fabian Fernandez.
 * http://entitiestodtos.codeplex.com
 * Licensed by Common Development and Distribution License (CDDL).
 * http://entitiestodtos.codeplex.com/license
 * Fabian Fernandez. 
 * http://www.linkedin.com/in/fabianfernandezb/en
 * */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EntitiesToDTOs.Domain;
using EntitiesToDTOs.Generators.Parameters;
using System.ComponentModel;
using EntitiesToDTOs.Helpers;
using EntitiesToDTOs.Events;
using EntitiesToDTOs.Properties;
using EntitiesToDTOs.Enums;
using EnvDTE;

namespace EntitiesToDTOs.Generators
{
    /// <summary>
    /// Provides functionality to generate Assemblers.
    /// </summary>
    internal class AssemblerGenerator
    {
        /// <summary>
        /// Generates Assemblers for DTOs generated using the parameters received.
        /// </summary>
        /// <param name="parameters">Parameters for the generation of Assemblers.</param>
        /// <param name="worker">BackgroundWorker reference.</param>
        public static void GenerateAssemblers(GenerateAssemblersParams parameters, BackgroundWorker worker)
        {
            LogManager.LogMethodStart();

            // Variables
            EditPoint objEditPoint; // EditPoint to reuse
            CodeNamespace objNamespace = null; // Namespace item to add Classes
            ProjectItem sourceFileItem = null; // Source File Item to save
            int assemblersGenerated = 0;

            // Report Progress
            worker.ReportProgress(0, new GeneratorOnProgressEventArgs(0,
                string.Format(Resources.Text_AssemblersGenerated, 
                    assemblersGenerated, parameters.EntitiesDTOs.Count)));

            // Add Reference to System.Core
            VisualStudioHelper.AddReferenceToProject(parameters.TargetProject,
                Resources.AssemblySystemCore, Resources.AssemblySystemCore);

            // Add Reference to System.Data.Entity
            VisualStudioHelper.AddReferenceToProject(parameters.TargetProject,
                Resources.AssemblySystemDataEntity, Resources.AssemblySystemDataEntity);

            if (parameters.IsServiceReady)
            {
                // Add Reference to System.Runtime.Serialization
                VisualStudioHelper.AddReferenceToProject(parameters.TargetProject,
                    Resources.AssemblySystemRuntimeSerialization, Resources.AssemblySystemRuntimeSerialization);
            }

            if (parameters.TargetProject.UniqueName != parameters.EDMXProject.UniqueName)
            {
                // Add Reference to EDMX Project
                VisualStudioHelper.AddReferenceToProject(parameters.TargetProject, parameters.EDMXProject);
            }

            if (parameters.TargetProject.UniqueName != parameters.DTOsTargetProject.UniqueName)
            {
                // Add Reference to DTOs Project
                VisualStudioHelper.AddReferenceToProject(parameters.TargetProject, parameters.DTOsTargetProject);
            }

            // Check Cancellation Pending
            if (GeneratorManager.CheckCancellationPending()) return;

            // Create Template Class File
            TemplateClass.CreateFile();

            // Imports to add to the Source File
            var importList = new List<SourceCodeImport>();
            importList.Add(new SourceCodeImport(Resources.AssemblySystemLinq));

            if (parameters.SourceNamespace != parameters.DTOsNamespace)
            {
                // Add import of DTOs namespace
                importList.Add(new SourceCodeImport(parameters.DTOsNamespace));
            }

            if (parameters.SourceNamespace != parameters.EntitiesNamespace)
            {
                // Add import of Entities namespace
                importList.Add(new SourceCodeImport(parameters.EntitiesNamespace));
            }

            // Generate Source File if type is One Source File
            if (parameters.SourceFileGenerationType == SourceFileGenerationTypeEnum.OneSourceFile)
            {
                sourceFileItem = null;

                // Generate Source and Get the Namespace item
                objNamespace = VisualStudioHelper.GenerateSourceAndGetNamespace(parameters.TargetProject,
                    parameters.SourceFileName, parameters.SourceFileHeaderComment,
                    parameters.SourceNamespace, parameters.IsServiceReady, out sourceFileItem);

                // Add import of System.Data.Objects.DataClasses (necessary for AssemblerBase)
                importList.Add(new SourceCodeImport(Resources.AssemblySystemDataObjectsDataClasses));

                // Add Imports to Source File
                VisualStudioHelper.AddImportsToSourceCode(ref sourceFileItem, importList);
            }

            // Check Cancellation Pending
            if (GeneratorManager.CheckCancellationPending()) return;

            // Set Assembler for all DTOs
            foreach (EntityDTO dto in parameters.EntitiesDTOs)
            {
                dto.SetAssembler();
            }

            // Check Cancellation Pending
            if (GeneratorManager.CheckCancellationPending()) return;

            // Loop through Entities DTOs
            foreach (EntityDTO dto in parameters.EntitiesDTOs)
            {
                // Generate Source File if type is Source File per Class
                if (parameters.SourceFileGenerationType == SourceFileGenerationTypeEnum.SourceFilePerClass)
                {
                    sourceFileItem = null;

                    // Generate Source and Get the Namespace item
                    objNamespace = VisualStudioHelper.GenerateSourceAndGetNamespace(
                        parameters.TargetProject, dto.Assembler.Name, parameters.SourceFileHeaderComment,
                        parameters.SourceNamespace, parameters.IsServiceReady, out sourceFileItem);

                    // Add Imports to Source File
                    VisualStudioHelper.AddImportsToSourceCode(ref sourceFileItem, importList);
                }

                // Instance creation code variables
                var toDTOInstanceCode = string.Empty;
                var toEntityInstanceCode = string.Empty;

                #region Generate instance creation code
                if (dto.DTOChilds != null && dto.DTOChilds.Count > 0)
                {
                    bool firstIfStatement = true;

                    // Loop DTO Child classes
                    foreach (EntityDTO dtoChild in dto.DTOChilds)
                    {
                        // Set if statement checking if the entity is of this type, if it is then invoke DTO Assembler ToDTO method
                        if (firstIfStatement)
                        {
                            firstIfStatement = false;

                            toDTOInstanceCode += string.Format(Resources.AssemblerToDTOInstanceIfChild, dtoChild.EntityName);
                            
                            toEntityInstanceCode += string.Format(Resources.AssemblerToEntityInstanceIfChild, dtoChild.DTOName);
                        }
                        else
                        {
                            toDTOInstanceCode += Environment.NewLine
                                + string.Format(Resources.AssemblerToDTOInstanceElseIfChild, dtoChild.EntityName);

                            toEntityInstanceCode += Environment.NewLine
                                + string.Format(Resources.AssemblerToEntityInstanceElseIfChild, dtoChild.DTOName);
                        }

                        toDTOInstanceCode += Environment.NewLine
                            + string.Format(Resources.AssemblerToDTOInstanceChild, dtoChild.EntityName);

                        toEntityInstanceCode += Environment.NewLine
                            + string.Format(Resources.AssemblerToEntityInstanceChild, dtoChild.DTOName);
                    }

                    if (dto.IsAbstract == false)
                    {
                        toDTOInstanceCode += Environment.NewLine + Resources.AssemblerToDTOInstanceElse + Environment.NewLine
                            + string.Format(Resources.AssemblerToDTOInstanceNew, dto.DTOName);

                        toEntityInstanceCode += Environment.NewLine + Resources.AssemblerToEntityInstanceElse + Environment.NewLine
                            + string.Format(Resources.AssemblerToEntityInstanceNew, dto.EntityName);
                    }
                }
                else if (dto.IsAbstract == false)
                {
                    // No childs, simple DTO instance

                    toDTOInstanceCode = string.Format(Resources.AssemblerToDTOInstanceNew, dto.DTOName);

                    toEntityInstanceCode = string.Format(Resources.AssemblerToEntityInstanceNew, dto.EntityName);
                }
                #endregion Generate instance creation code

                // Property assignments code variables
                var toDTOAssignmentsCode = string.Empty;
                var toEntityAssignmentsCode = string.Empty;

                #region Generate property assignments code
                if ((dto.DTOChilds != null && dto.DTOChilds.Count > 0)
                    || dto.IsAbstract == false)
                {
                    foreach (var property in dto.Properties)
                    {
                        bool includeToDTO = true;
                        bool includeToEntity = true;

                        // Define assignments (with defaults)
                        string toDTOAssignment = string.Format(Resources.AssemblerToDTOAssignmentEntityProp, property.PropertyNameEDMX);
                        string toEntityAssignment = string.Format(Resources.AssemblerToEntityAssignmentDTOProp, property.PropertyName);

                        if (property.IsNavigation)
                        {
                            string typeToCheck = property.PropertyType;
                            if (property.IsList)
                            {
                                typeToCheck = property.ListOf;
                            }

                            // Check type is DTO
                            EntityDTO dtoPropType = parameters.EntitiesDTOs.FirstOrDefault(e => e.DTOName == typeToCheck);

                            if (dtoPropType != null)
                            {
                                // We do not map when it is a Navigation by Type
                                // The User has to map navigations manually on partial methods if needed
                                includeToDTO = false;
                                includeToEntity = false;
                            }
                            else
                            {
                                // This indicates that the Navigation Property was created using navigation by Key
                                // i.e.: property.IsNavigation and dtoPropType is null

                                // We do not map on method ToEntity when it is Navigation by Key
                                // The User has to map navigations manually on partial methods of ToEntity if needed
                                includeToEntity = false;

                                // Get the Key of the Target DTO of the Navigation
                                // Always one Key as we do not let the user to create a navigation to 
                                // a target DTO who has multiple Key Properties using Navigation by Key option.
                                EntityDTOProperty dtoTargetKey = property.NavigatesToDTO.Properties.First(p => p.IsKey == true);

                                if (property.IsList)
                                {
                                    toDTOAssignment = string.Format(Resources.AssemblerToDTOAssignmentEntityNavKeyList, property.PropertyNameEDMX, dtoTargetKey.PropertyNameEDMX);
                                }
                                else
                                {
                                    toDTOAssignment = string.Format(Resources.AssemblerToDTOAssignmentEntityNavKey, property.PropertyNameEDMX, dtoTargetKey.PropertyNameEDMX);
                                }
                            }
                        }

                        if (includeToDTO)
                        {
                            // ToDTO assignment => dto.prop = ...
                            toDTOAssignmentsCode += Environment.NewLine
                                + string.Format(Resources.AssemblerToDTOAssignment, property.PropertyName, toDTOAssignment);
                        }

                        if (includeToEntity)
                        {
                            // ToEntity assignment => entity.prop = ...
                            toEntityAssignmentsCode += Environment.NewLine
                                + string.Format(Resources.AssemblerToEntityAssignment, property.PropertyNameEDMX, toEntityAssignment);
                        }

                    } // End loop through DTO properties

                } // End If check to generate assignments
                #endregion Generate property assignments code

                // Get the start point where to insert the Assembler code
                objEditPoint = objNamespace.EndPoint.CreateEditPoint();
                objEditPoint.LineUp();
                objEditPoint.EndOfLine();
                objEditPoint.Insert(Environment.NewLine);

                // Get the Assembler's code
                string assemblerCode = dto.Assembler.GetAssemblerCode(toDTOInstanceCode, toEntityInstanceCode,
                    toDTOAssignmentsCode, toEntityAssignmentsCode);

                // Insert Assembler's code
                objEditPoint.Insert(assemblerCode);

                // Format code
                objEditPoint.StartOfDocument();
                EditPoint endOfDocument = objNamespace.EndPoint.CreateEditPoint();
                endOfDocument.EndOfDocument();
                objEditPoint.SmartFormat(endOfDocument);
                
                // Save changes to Source File Item
                sourceFileItem.Save();

                // Count Assemblers generated
                assemblersGenerated++;

                // Report Progress
                int progress = ((assemblersGenerated * 100) / parameters.EntitiesDTOs.Count);
                if (progress < 100)
                {
                    worker.ReportProgress(progress, new GeneratorOnProgressEventArgs(progress,
                        string.Format(Resources.Text_AssemblersGenerated, assemblersGenerated, parameters.EntitiesDTOs.Count)));
                }

                // Check Cancellation Pending
                if (GeneratorManager.CheckCancellationPending()) return;

            } // END Loop through Entities DTOs

            // Save Target Project
            parameters.TargetProject.Save();

            // Delete Template Class File
            TemplateClass.Delete();

            // Report Progress
            worker.ReportProgress(100, new GeneratorOnProgressEventArgs(100,
                string.Format(Resources.Text_AssemblersGenerated, 
                    assemblersGenerated, parameters.EntitiesDTOs.Count)));

            LogManager.LogMethodEnd();
        }
    }
}